// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.linqingying.gitcode.api

import com.intellij.collaboration.api.util.LinkHttpHeaderValue

import com.intellij.util.ThrowableConvertor
import com.linqingying.gitcode.api.GitCodeApiRequest.*
import com.linqingying.gitcode.api.data.*
import com.linqingying.gitcode.api.data.commit.GCCommitFile
import com.linqingying.gitcode.api.data.commit.GCCommitFiles
import com.linqingying.gitcode.api.data.pullrequest.GCPullRequestRestIdOnly
import com.linqingying.gitcode.api.data.request.*
import com.linqingying.gitcode.api.util.GitCodeApiPagesLoader
import com.linqingying.gitcode.api.util.GitCodeApiSearchQueryBuilder
import com.linqingying.gitcode.api.util.GitCodeApiUrlQueryBuilder
import com.linqingying.gitcode.pullrequest.data.GCPRIdentifier
import com.linqingying.gitcode.pullrequest.data.GCPRSearchQuery
import java.awt.image.BufferedImage

/**
 * Collection of factory methods for API requests used in plugin
 * TODO: improve url building (DSL?)
 */
object GitCodeApiRequests {

    @JvmStatic
    fun getBytes(url: String): GitCodeApiRequest<ByteArray> = object : Get<ByteArray>(url) {
        override fun extractResult(response: GitCodeApiResponse): ByteArray {
            return response.handleBody(ThrowableConvertor {
                it.readAllBytes()
            })
        }
    }

    object CurrentUser : Entity("/user") {
        @JvmStatic
        fun get(server: GitCodeServerPath) = get(getUrl(server, urlSuffix))

        @JvmStatic
        fun get(url: String) = Get.json<GitCodeAuthenticatedUser>(url).withOperationName("get profile information")

        @JvmStatic
        fun getAvatar(url: String): GitCodeApiRequest<BufferedImage> = object : Get<BufferedImage>(url) {
            override fun extractResult(response: GitCodeApiResponse): BufferedImage {
                return response.handleBody {
                    GitCodeApiContentHelper.loadImage(it)
                }
            }
        }.withOperationName("get profile avatar")

        object Repos : Entity("/repos") {
            @JvmOverloads
            @JvmStatic
            fun pages(
                server: GitCodeServerPath,
                type: Type = Type.DEFAULT,
                visibility: Visibility = Visibility.DEFAULT,
                affiliation: Affiliation = Affiliation.DEFAULT,
                pagination: GitCodeRequestPagination? = null
            ) =
                GitCodeApiPagesLoader.Request(get(server, type, visibility, affiliation, pagination), ::get)

            @JvmOverloads
            @JvmStatic
            fun get(
                server: GitCodeServerPath,
                type: Type = Type.DEFAULT,
                visibility: Visibility = Visibility.DEFAULT,
                affiliation: Affiliation = Affiliation.DEFAULT,
                pagination: GitCodeRequestPagination? = null
            ): GitCodeApiRequest<GitCodeResponsePage<GitCodeRepo>> {
                if (type != Type.DEFAULT && (visibility != Visibility.DEFAULT || affiliation != Affiliation.DEFAULT)) {
                    throw IllegalArgumentException("Param 'type' should not be used together with 'visibility' or 'affiliation'")
                }

                return get(
                    getUrl(
                        server, CurrentUser.urlSuffix, urlSuffix,
                        getQuery(
                            type.toString(),
                            visibility.toString(),
                            affiliation.toString(),
                            pagination?.toString().orEmpty()
                        )
                    )
                )
            }

            @JvmStatic
            fun get(url: String) = Get.jsonPage<GitCodeRepo>(url).withOperationName("get user repositories")

            @JvmStatic
            fun create(
                server: GitCodeServerPath,
                name: String,
                description: String,
                private: Boolean,
                autoInit: Boolean? = null
            ): GitCodeApiRequest<GitCodeRepo> {
            return Post.json<GitCodeRepo>(
                    getUrl(server, CurrentUser.urlSuffix, urlSuffix),
                    GitCodeRepoRequest(name, description, private, autoInit)
                )
                    .withOperationName("create user repository")


            }

        }


    }
    object Users : Entity("/users") {
        object Orgs : Entity("/orgs") {
            @JvmOverloads
            @JvmStatic
            fun pages(server: GitCodeServerPath, pagination: GitCodeRequestPagination? = null) =
                GitCodeApiPagesLoader.Request(get(server, pagination), ::get)

            fun get(server: GitCodeServerPath, pagination: GitCodeRequestPagination? = null) =
                get(getUrl(server, Users.urlSuffix, urlSuffix, getQuery(pagination?.toString().orEmpty())))

            fun get(url: String) = Get.jsonPage<GitCodeOrg>(url).withOperationName("get user organizations")
        }
    }

    object Organisations : Entity("/orgs") {

        object Repos : Entity("/repos") {
            @JvmStatic
            fun pages(server: GitCodeServerPath, organisation: String, pagination: GitCodeRequestPagination? = null) =
                GitCodeApiPagesLoader.Request(get(server, organisation, pagination), ::get)

            @JvmOverloads
            @JvmStatic
            fun get(server: GitCodeServerPath, organisation: String, pagination: GitCodeRequestPagination? = null) =
                get(
                    getUrl(
                        server,
                        Organisations.urlSuffix,
                        "/",
                        organisation,
                        urlSuffix,
                        getQuery(pagination?.toString().orEmpty())
                    )
                )

            @JvmStatic
            fun get(url: String) = Get.jsonPage<GitCodeRepo>(url).withOperationName("get organisation repositories")

            @JvmStatic
            fun create(
                server: GitCodeServerPath,
                organisation: String,
                name: String,
                description: String,
                private: Boolean
            ) =
                Post.json<GitCodeRepo>(
                    getUrl(server, Organisations.urlSuffix, "/", organisation, urlSuffix),
                    GitCodeRepoRequest(name, description, private, null)
                )
                    .withOperationName("create organisation repository")
        }
    }

    object Repos : Entity("/repos") {
        @JvmStatic
        fun get(server: GitCodeServerPath, username: String, repoName: String) =
            Get.Optional.json<GitCodeRepoDetailed>(getUrl(server, urlSuffix, "/$username/$repoName"))
                .withOperationName("get information for repository $username/$repoName")

        @JvmStatic
        fun get(url: String) =
            Get.Optional.json<GitCodeRepoDetailed>(url).withOperationName("get information for repository $url")

        @JvmStatic
        fun delete(server: GitCodeServerPath, username: String, repoName: String) =
            delete(
                getUrl(
                    server,
                    urlSuffix,
                    "/$username/$repoName"
                )
            ).withOperationName("delete repository $username/$repoName")

        @JvmStatic
        fun delete(url: String) = Delete.json<Unit>(url).withOperationName("delete repository at $url")

        object Content : Entity("/contents") {

            @JvmOverloads
            @JvmStatic
            fun list(
                server: GitCodeServerPath,
                username: String,
                repoName: String,
                path: String,
                ref: String? = null,
                pagination: GitCodeRequestPagination
            ) =
                list(
                    getUrl(
                        server,
                        Repos.urlSuffix,
                        "/$username/$repoName",
                        urlSuffix,
                        "/$path",
                        getQuery(if (ref == null) "" else "ref=$ref", pagination.toString())
                    )
                )

            @JvmOverloads
            @JvmStatic
            fun get(server: GitCodeServerPath, username: String, repoName: String, path: String, ref: String? = null) =
                get(
                    getUrl(
                        server,
                        Repos.urlSuffix,
                        "/$username/$repoName",
                        urlSuffix,
                        "/$path",
                        getQuery(if (ref == null) "" else "ref=$ref")
                    )
                )

            @JvmStatic
            fun list(url: String) = Get.jsonPage<GitCodeContent>(url).withOperationName("get content")

            @JvmStatic
            fun get(url: String) = Get.json<GitCodeContent>(url).withOperationName("get file")

        }

        object Branches : Entity("/branches") {
            @JvmStatic
            fun pages(server: GitCodeServerPath, username: String, repoName: String) =
                GitCodeApiPagesLoader.Request(get(server, username, repoName), ::get)

            @JvmOverloads
            @JvmStatic
            fun get(
                server: GitCodeServerPath,
                username: String,
                repoName: String,
                pagination: GitCodeRequestPagination? = null
            ) =
                get(
                    getUrl(
                        server,
                        Repos.urlSuffix,
                        "/$username/$repoName",
                        urlSuffix,
                        getQuery(pagination?.toString().orEmpty())
                    )
                )

            @JvmStatic
            fun get(url: String) = Get.jsonPage<GitCodeBranch>(url).withOperationName("get branches")
        }

        object Tags : Entity("/tags") {
            @JvmStatic
            fun pages(server: GitCodeServerPath, username: String, repoName: String) =
                GitCodeApiPagesLoader.Request(get(server, username, repoName), ::get)

            @JvmOverloads
            @JvmStatic
            fun get(
                server: GitCodeServerPath,
                username: String,
                repoName: String,
                pagination: GitCodeRequestPagination? = null
            ) =
                get(
                    getUrl(
                        server,
                        Repos.urlSuffix,
                        "/$username/$repoName",
                        urlSuffix,
                        getQuery(pagination?.toString().orEmpty())
                    )
                )

            @JvmStatic
            fun get(url: String) = Get.jsonPage<GitCodeTagInfo>(url).withOperationName("get tags")
        }

        object Commits : Entity("/commits") {

            @JvmStatic
            fun compare(repository: GCRepositoryCoordinates, refA: String, refB: String) =
                Get.json<GCCommitsCompareResult>(getUrl(repository, "/compare/$refA...$refB"))
                    .withOperationName("compare refs")

            @JvmStatic
            fun getDiffFiles(
                repository: GCRepositoryCoordinates,
                ref: String
            ): GitCodeApiRequest<GitCodeResponsePage<GCCommitFile>> =
                getDiffFiles(getUrl(repository, urlSuffix, "/$ref"))

            @JvmStatic
            fun getDiffFiles(url: String): GitCodeApiRequest<GitCodeResponsePage<GCCommitFile>> =
                object : Get<GitCodeResponsePage<GCCommitFile>>(url) {
                    override fun extractResult(response: GitCodeApiResponse): GitCodeResponsePage<GCCommitFile> {
                        val list = response.readBody(ThrowableConvertor {
                            GitCodeApiContentHelper.readJsonObject(
                                it,
                                GCCommitFiles::class.java
                            )
                        })
                            .files
                        val linkHeader =
                            response.findHeader(LinkHttpHeaderValue.HEADER_NAME)?.let(LinkHttpHeaderValue::parse)
                        return GitCodeResponsePage(list, linkHeader)
                    }
                }.withOperationName("get files for ref")

            @JvmStatic
            fun getDiff(repository: GCRepositoryCoordinates, refA: String, refB: String) =
                object : Get<String>(
                    getUrl(repository, "/compare/$refA...$refB"),
                    GitCodeApiContentHelper.V3_DIFF_JSON_MIME_TYPE
                ) {
                    override fun extractResult(response: GitCodeApiResponse): String {
                        return response.handleBody(ThrowableConvertor {
                            it.reader().use { it.readText() }
                        })
                    }
                }.withOperationName("get diff between refs")
        }

        object Forks : Entity("/forks") {

            @JvmStatic
            fun create(server: GitCodeServerPath, username: String, repoName: String) =
                Post.json<GitCodeRepo>(getUrl(server, Repos.urlSuffix, "/$username/$repoName", urlSuffix), Any())
                    .withOperationName("fork repository $username/$repoName for cuurent user")

            @JvmStatic
            fun pages(server: GitCodeServerPath, username: String, repoName: String) =
                GitCodeApiPagesLoader.Request(get(server, username, repoName), ::get)

            @JvmOverloads
            @JvmStatic
            fun get(
                server: GitCodeServerPath,
                username: String,
                repoName: String,
                pagination: GitCodeRequestPagination? = null
            ) =
                get(
                    getUrl(
                        server,
                        Repos.urlSuffix,
                        "/$username/$repoName",
                        urlSuffix,
                        getQuery(pagination?.toString().orEmpty())
                    )
                )

            @JvmStatic
            fun get(url: String) = Get.jsonPage<GitCodeRepo>(url).withOperationName("get forks")
        }

        object Assignees : Entity("/assignees") {

            @JvmStatic
            fun pages(server: GitCodeServerPath, username: String, repoName: String) =
                GitCodeApiPagesLoader.Request(get(server, username, repoName), ::get)

            @JvmOverloads
            @JvmStatic
            fun get(
                server: GitCodeServerPath,
                username: String,
                repoName: String,
                pagination: GitCodeRequestPagination? = null
            ) =
                get(
                    getUrl(
                        server,
                        Repos.urlSuffix,
                        "/$username/$repoName",
                        urlSuffix,
                        getQuery(pagination?.toString().orEmpty())
                    )
                )

            @JvmStatic
            fun get(url: String) = Get.jsonPage<GitCodeUser>(url).withOperationName("get assignees")
        }

        object Labels : Entity("/labels") {

            @JvmStatic
            fun pages(server: GitCodeServerPath, username: String, repoName: String) =
                GitCodeApiPagesLoader.Request(get(server, username, repoName), ::get)

            @JvmOverloads
            @JvmStatic
            fun get(
                server: GitCodeServerPath,
                username: String,
                repoName: String,
                pagination: GitCodeRequestPagination? = null
            ) =
                get(
                    getUrl(
                        server,
                        Repos.urlSuffix,
                        "/$username/$repoName",
                        urlSuffix,
                        getQuery(pagination?.toString().orEmpty())
                    )
                )

            @JvmStatic
            fun get(url: String) = Get.jsonPage<GitCodeIssueLabel>(url).withOperationName("get assignees")
        }

        object Collaborators : Entity("/collaborators") {

            @JvmStatic
            fun pages(server: GitCodeServerPath, username: String, repoName: String) =
                GitCodeApiPagesLoader.Request(get(server, username, repoName), ::get)

            @JvmOverloads
            @JvmStatic
            fun get(
                server: GitCodeServerPath,
                username: String,
                repoName: String,
                pagination: GitCodeRequestPagination? = null
            ) =
                get(
                    getUrl(
                        server,
                        Repos.urlSuffix,
                        "/$username/$repoName",
                        urlSuffix,
                        getQuery(pagination?.toString().orEmpty())
                    )
                )

            @JvmStatic
            fun get(url: String) = Get.jsonPage<GitCodeUserWithPermissions>(url).withOperationName("get collaborators")

            @JvmStatic
            fun add(server: GitCodeServerPath, username: String, repoName: String, collaborator: String) =
                Put.json<Unit>(getUrl(server, Repos.urlSuffix, "/$username/$repoName", urlSuffix, "/", collaborator))
        }

        object Issues : Entity("/issues") {

            @JvmStatic
            fun create(
                server: GitCodeServerPath,
                username: String,
                repoName: String,
                title: String,
                body: String? = null,
                milestone: Long? = null,
                labels: List<String>? = null,
                assignees: List<String>? = null
            ) =
                Post.json<GitCodeIssue>(
                    getUrl(server, Repos.urlSuffix, "/$username/$repoName", urlSuffix),
                    GitCodeCreateIssueRequest(title, body, milestone, labels, assignees)
                )

            @JvmStatic
            fun pages(
                server: GitCodeServerPath, username: String, repoName: String,
                state: String? = null, assignee: String? = null
            ) = GitCodeApiPagesLoader.Request(
                get(
                    server, username, repoName,
                    state, assignee
                ), ::get
            )

            @JvmStatic
            fun get(
                server: GitCodeServerPath, username: String, repoName: String,
                state: String? = null, assignee: String? = null, pagination: GitCodeRequestPagination? = null
            ) =
                get(
                    getUrl(
                        server, Repos.urlSuffix, "/$username/$repoName", urlSuffix,
                        GitCodeApiUrlQueryBuilder.urlQuery {
                            param("state", state); param("assignee", assignee); param(
                            pagination
                        )
                        })
                )

            @JvmStatic
            fun get(url: String) = Get.jsonPage<GitCodeIssue>(url).withOperationName("get issues in repository")

            @JvmStatic
            fun get(server: GitCodeServerPath, username: String, repoName: String, id: String) =
                Get.Optional.json<GitCodeIssue>(
                    getUrl(
                        server,
                        Repos.urlSuffix,
                        "/$username/$repoName",
                        urlSuffix,
                        "/",
                        id
                    )
                )

            @JvmStatic
            fun updateState(server: GitCodeServerPath, username: String, repoName: String, id: String, open: Boolean) =
                Patch.json<GitCodeIssue>(
                    getUrl(server, Repos.urlSuffix, "/$username/$repoName", urlSuffix, "/", id),
                    GitCodeChangeIssueStateRequest(if (open) "open" else "closed")
                )

            @JvmStatic
            fun updateAssignees(
                server: GitCodeServerPath,
                username: String,
                repoName: String,
                id: String,
                assignees: Collection<String>
            ) =
                Patch.json<GitCodeIssue>(
                    getUrl(server, Repos.urlSuffix, "/$username/$repoName", urlSuffix, "/", id),
                    GitCodeAssigneesCollectionRequest(assignees)
                )

            object Comments : Entity("/comments") {
                @JvmStatic
                fun create(repository: GCRepositoryCoordinates, issueId: Long, body: String) =
                    create(
                        repository.serverPath,
                        repository.repositoryPath.owner,
                        repository.repositoryPath.repository,
                        issueId.toString(),
                        body
                    )

                @JvmStatic
                fun create(
                    server: GitCodeServerPath,
                    username: String,
                    repoName: String,
                    issueId: String,
                    body: String
                ) =
                    Post.json<GitCodeIssueCommentWithHtml>(
                        getUrl(
                            server,
                            Repos.urlSuffix,
                            "/$username/$repoName",
                            Issues.urlSuffix,
                            "/",
                            issueId,
                            urlSuffix
                        ),
                        GitCodeCreateIssueCommentRequest(body),
                        GitCodeApiContentHelper.V3_HTML_JSON_MIME_TYPE
                    )

                @JvmStatic
                fun pages(server: GitCodeServerPath, username: String, repoName: String, issueId: String) =
                    GitCodeApiPagesLoader.Request(get(server, username, repoName, issueId), ::get)

                @JvmStatic
                fun pages(url: String) = GitCodeApiPagesLoader.Request(get(url), ::get)

                @JvmStatic
                fun get(
                    server: GitCodeServerPath, username: String, repoName: String, issueId: String,
                    pagination: GitCodeRequestPagination? = null
                ) =
                    get(
                        getUrl(
                            server, Repos.urlSuffix, "/$username/$repoName", Issues.urlSuffix, "/", issueId, urlSuffix,
                            GitCodeApiUrlQueryBuilder.urlQuery { param(pagination) })
                    )

                @JvmStatic
                fun get(url: String) =
                    Get.jsonPage<GitCodeIssueCommentWithHtml>(url, GitCodeApiContentHelper.V3_HTML_JSON_MIME_TYPE)
                        .withOperationName("get comments for issue")
            }

            object Labels : Entity("/labels") {
                @JvmStatic
                fun replace(
                    server: GitCodeServerPath,
                    username: String,
                    repoName: String,
                    issueId: String,
                    labels: Collection<String>
                ) =
                    Put.jsonList<GitCodeIssueLabel>(
                        getUrl(
                            server,
                            Repos.urlSuffix,
                            "/$username/$repoName",
                            Issues.urlSuffix,
                            "/",
                            issueId,
                            urlSuffix
                        ),
                        GitCodeLabelsCollectionRequest(labels)
                    )
            }
        }

        object PullRequests : Entity("/pulls") {

            @JvmStatic
            fun find(
                repository: GCRepositoryCoordinates,
                state: GitCodeIssueState? = null,
                baseRef: String? = null,
                headRef: String? = null
            ): GitCodeApiRequest<GitCodeResponsePage<GCPullRequestRestIdOnly>> =
                Get.jsonPage<GCPullRequestRestIdOnly>(getUrl(repository, urlSuffix, buildQuery {
                    put("state", state?.toString())
                    put("base", baseRef)
                    put("head", headRef)
                })).withOperationName("find pull requests")

            @JvmStatic
            fun update(
                serverPath: GitCodeServerPath, username: String, repoName: String, number: Long,
                title: String? = null,
                body: String? = null,
                state: GitCodeIssueState? = null,
                base: String? = null,
                maintainerCanModify: Boolean? = null
            ) =
                Patch.json<Any>(
                    getUrl(serverPath, Repos.urlSuffix, "/$username/$repoName", urlSuffix, "/$number"),
                    GitCodePullUpdateRequest(title, body, state, base, maintainerCanModify)
                )
                    .withOperationName("update pull request $number")

            @JvmStatic
            fun merge(
                server: GitCodeServerPath, repoPath: GCRepositoryPath, number: Long,
                commitSubject: String, commitBody: String, headSha: String
            ) =
                Put.json<Unit>(
                    getUrl(server, Repos.urlSuffix, "/$repoPath", urlSuffix, "/$number", "/merge"),
                    GitCodePullRequestMergeRequest(
                        commitSubject,
                        commitBody,
                        headSha,
                        GitCodePullRequestMergeMethod.merge
                    )
                )
                    .withOperationName("merge pull request ${number}")

            @JvmStatic
            fun squashMerge(
                server: GitCodeServerPath, repoPath: GCRepositoryPath, number: Long,
                commitSubject: String, commitBody: String, headSha: String
            ) =
                Put.json<Unit>(
                    getUrl(server, Repos.urlSuffix, "/$repoPath", urlSuffix, "/$number", "/merge"),
                    GitCodePullRequestMergeRequest(
                        commitSubject,
                        commitBody,
                        headSha,
                        GitCodePullRequestMergeMethod.squash
                    )
                )
                    .withOperationName("squash and merge pull request ${number}")

            @JvmStatic
            fun rebaseMerge(
                server: GitCodeServerPath, repoPath: GCRepositoryPath, number: Long,
                headSha: String
            ) =
                Put.json<Unit>(
                    getUrl(server, Repos.urlSuffix, "/$repoPath", urlSuffix, "/$number", "/merge"),
                    GitCodePullRequestMergeRebaseRequest(headSha)
                )
                    .withOperationName("rebase and merge pull request ${number}")

            @JvmStatic
            fun getListETag(server: GitCodeServerPath, repoPath: GCRepositoryPath) =
                object : Get<String?>(
                    getUrl(
                        server, Repos.urlSuffix, "/$repoPath", urlSuffix,
                        GitCodeApiUrlQueryBuilder.urlQuery { param(GitCodeRequestPagination(pageSize = 1)) })
                ) {
                    override fun extractResult(response: GitCodeApiResponse) = response.findHeader("ETag")
                }.withOperationName("get pull request list ETag")

            @JvmStatic
            fun getDiffFiles(
                repository: GCRepositoryCoordinates,
                id: GCPRIdentifier
            ): GitCodeApiRequest<GitCodeResponsePage<GCCommitFile>> =
                getDiffFiles(getUrl(repository, urlSuffix, "/${id.number}", "/files"))

            @JvmStatic
            fun getDiffFiles(url: String): GitCodeApiRequest<GitCodeResponsePage<GCCommitFile>> =
                Get.jsonPage<GCCommitFile>(url).withOperationName("get files for pull request")

            object Reviewers : Entity("/requested_reviewers") {
                @JvmStatic
                fun add(
                    server: GitCodeServerPath, username: String, repoName: String, number: Long,
                    reviewers: Collection<String>, teamReviewers: List<String>
                ) =
                    Post.json<Unit>(
                        getUrl(
                            server,
                            Repos.urlSuffix,
                            "/$username/$repoName",
                            PullRequests.urlSuffix,
                            "/$number",
                            urlSuffix
                        ),
                        GitCodeReviewersCollectionRequest(reviewers, teamReviewers)
                    )

                @JvmStatic
                fun remove(
                    server: GitCodeServerPath, username: String, repoName: String, number: Long,
                    reviewers: Collection<String>, teamReviewers: List<String>
                ) =
                    Delete.json<Unit>(
                        getUrl(
                            server,
                            Repos.urlSuffix,
                            "/$username/$repoName",
                            PullRequests.urlSuffix,
                            "/$number",
                            urlSuffix
                        ),
                        GitCodeReviewersCollectionRequest(reviewers, teamReviewers)
                    )
            }
        }
    }

    object Gists : Entity("/gists") {
        @JvmStatic
        fun create(
            server: GitCodeServerPath,
            contents: List<GitCodeGistRequest.FileContent>, description: String, public: Boolean
        ) =
            Post.json<GitCodeGist>(
                getUrl(server, urlSuffix),
                GitCodeGistRequest(contents, description, public)
            )
                .withOperationName("create gist")

        @JvmStatic
        fun get(server: GitCodeServerPath, id: String) =
            Get.Optional.json<GitCodeGist>(getUrl(server, urlSuffix, "/$id"))
                .withOperationName("get gist $id")

        @JvmStatic
        fun delete(server: GitCodeServerPath, id: String) = Delete.json<Unit>(getUrl(server, urlSuffix, "/$id"))
            .withOperationName("delete gist $id")
    }

    object Search : Entity("/search") {
        object Issues : Entity("/issues") {
            @JvmStatic
            fun pages(
                server: GitCodeServerPath,
                repoPath: GCRepositoryPath?,
                state: String?,
                assignee: String?,
                query: String?
            ) =
                GitCodeApiPagesLoader.Request(get(server, repoPath, state, assignee, query), ::get)

            @JvmStatic
            fun get(
                server: GitCodeServerPath,
                repoPath: GCRepositoryPath?,
                state: String?,
                assignee: String?,
                query: String?,
                pagination: GitCodeRequestPagination? = null
            ) =
                get(
                    getUrl(
                        server, Search.urlSuffix, urlSuffix,
                        GitCodeApiUrlQueryBuilder.urlQuery {
                            param("q", GitCodeApiSearchQueryBuilder.searchQuery {
                                term(GCPRSearchQuery.QualifierName.repo.createTerm(repoPath?.toString().orEmpty()))
                                term(GCPRSearchQuery.QualifierName.state.createTerm(state.orEmpty()))
                                term(GCPRSearchQuery.QualifierName.assignee.createTerm(assignee.orEmpty()))
                                query(query)
                            })
                            param(pagination)
                        })
                )

            @JvmStatic
            fun get(url: String) =
                Get.jsonSearchPage<GitCodeSearchedIssue>(url).withOperationName("search issues in repository")
        }
    }

    object Emojis : Entity("/emojis") {
        fun loadNameToUrlMap(server: GitCodeServerPath): GitCodeApiRequest<Map<String, String>> =
            Get.JsonMap(getUrl(server, urlSuffix))

        fun loadImage(url: String): GitCodeApiRequest<BufferedImage> =
            object : Get<BufferedImage>(url) {
                override fun extractResult(response: GitCodeApiResponse) =
                    response.handleBody(ThrowableConvertor(GitCodeApiContentHelper::loadImage))
            }
    }

    abstract class Entity(val urlSuffix: String)

    private fun getUrl(server: GitCodeServerPath, suffix: String) = server.toApiUrl() + suffix

    private fun getUrl(repository: GCRepositoryCoordinates, vararg suffixes: String) =
        getUrl(repository.serverPath, Repos.urlSuffix, "/", repository.repositoryPath.toString(), *suffixes)

    fun getUrl(server: GitCodeServerPath, vararg suffixes: String) =
        StringBuilder(server.toApiUrl()).append(*suffixes).toString()

    private fun buildQuery(builder: MutableMap<String, String?>.() -> Unit): String {
        val parts = mutableMapOf<String, String?>().apply(builder).mapNotNull { (key, value) ->
            if (value != null) "${key}=${value}"
            else null
        }
        return getQuery(parts)
    }

    private fun getQuery(vararg queryParts: String): String = getQuery(queryParts.toList())

    private fun getQuery(queryParts: Iterable<String>): String {
        val builder = StringBuilder()
        for (part in queryParts) {
            if (part.isEmpty()) continue
            if (builder.isEmpty()) builder.append("?")
            else builder.append("&")
            builder.append(part)
        }
        return builder.toString()
    }
}
